using Game.Networking;
using Game.Networking.Server;
using System.Collections.Generic;
using UnityEngine;

namespace Game {
	public class ShipRadar : IRadar {
		const float NearScanRange   = 60f;
		const float RaycastRange    = 85f;
		const float TrackerRange    = 140f;
		const float StaticScanRange = 200f;

		const float EnergyPerSetTracked = 20;
		const float EnergyPerTrackedUpdate = 0.5f;
		const float EnergyPerNearScan = 50;
		const float EnergyPerStaticScan = 50;
		const float EnergyPerRaycast = 0.1f;

		ShipCore   _owner;
		Collider[] _tmpColliderArray = new Collider[64];
		HashSet<uint> _tmpEntitySet = new HashSet<uint>(64);
		uint       _trackedObjectId;

		public ShipRadar(ShipCore owner) {
			_owner = owner;
		}

		public bool SetTrackedObject(uint objectId) {
			if ( !_owner.Reactor.HasEnergy(EnergyPerSetTracked) ) {
				return false;
			}
			_owner.Reactor.UseEnergy(EnergyPerSetTracked);
			_trackedObjectId = objectId;
			return true;
		}

		public void ResetTrackedObject() {
			_trackedObjectId = 0;
		}

		public bool IsTrackedObjectAlive() {
			if ( _trackedObjectId == 0 ) {
				return false;
			}
			var e = ServerEntityController.Instance.GetEntityById(_trackedObjectId);
			return e;
		}

		public EntitySummary CheckTrackedObject() {
			if ( NeedResetTrackedObject() ) {
				ResetTrackedObject();
				return default;
			}

			if ( !_owner.Reactor.HasEnergy(EnergyPerTrackedUpdate) ) {
				return default;
			}
			_owner.Reactor.UseEnergy(EnergyPerTrackedUpdate);

			var e = ServerEntityController.Instance.GetEntityById(_trackedObjectId);
			if ( !e ) {
				return default;
			}
			return GetSummaryForEntity(e);
		}

		public bool NearScan(float maxRange, List<EntitySummary> result) {
			result.Clear();
			var range = Mathf.Min(maxRange, NearScanRange);
			var requiredEnergy = Mathf.InverseLerp(range, 0, NearScanRange) * EnergyPerNearScan;
			if ( !_owner.Reactor.HasEnergy(requiredEnergy) ) {
				return false;
			}
			_owner.Reactor.UseEnergy(requiredEnergy);
						
			ScanInternal(range, CommonStaticData.Instance.AllCollisionsMask, result);
			return true;
		}

		public bool ScanForLargeObjects(float maxRange, List<EntitySummary> result) {
			result.Clear();
			var range = Mathf.Min(maxRange, StaticScanRange);
			var requiredEnergy = Mathf.InverseLerp(range, 0, NearScanRange) * EnergyPerStaticScan;
			if ( !_owner.Reactor.HasEnergy(requiredEnergy) ) {
				return false;
			}
			_owner.Reactor.UseEnergy(requiredEnergy);

			ScanInternal(range, CommonStaticData.Instance.StaticEntitiesMask, result);
			return true;
		}

		void ScanInternal(float range, LayerMask colMask, List<EntitySummary> result) {
			var ownId = _owner.ID;
			_tmpEntitySet.Clear();
			var count = Physics.OverlapSphereNonAlloc(_owner.transform.position, range, _tmpColliderArray, colMask, QueryTriggerInteraction.Ignore);
			for ( int i = 0; i < count; i++ ) {
				var entity = _tmpColliderArray[i].GetComponent<Entity>();
				if ( !entity || entity.ID == ownId || _tmpEntitySet.Contains(entity.ID) ) {
					continue;
				}
				_tmpEntitySet.Add(entity.ID);
				var summary = GetSummaryForEntity(entity);
				result.Add(summary);
			}
		}

		public bool Raycast(Vector3 direction, float maxDistance, out EntitySummary hitObject) {
			if ( !_owner.Reactor.HasEnergy(EnergyPerRaycast) ) {
				hitObject = default;
				return false;
			}
			_owner.Reactor.UseEnergy(EnergyPerRaycast);

			maxDistance = Mathf.Clamp(maxDistance, 0, RaycastRange);
			if ( Physics.Raycast(_owner.transform.position, _owner.transform.TransformDirection(direction), out var hit, maxDistance, CommonStaticData.Instance.AllCollisionsMask, QueryTriggerInteraction.Ignore) ) {
				var entity = hit.collider.GetComponent<Entity>();
				if ( !entity ) {
					hitObject =  new EntitySummary();
					hitObject.Type = EntityType.Unknown;
					return true;
				}
				hitObject = GetSummaryForEntity(entity);
				return true;
			}
			hitObject = default;
			return false;
		}

		public bool ForwardRaycast(float maxDistance, out EntitySummary hitObject) {
			return Raycast(Vector3.forward, maxDistance, out hitObject);
		}

		EntitySummary GetSummaryForEntity(Entity entity) {
			var summary = new EntitySummary();
			summary.ID = entity.ID;
			summary.Position = entity.transform.position;
			if ( entity?.Rb ) {
				summary.Velocity = entity?.Rb?.velocity ?? Vector3.zero;
			}			
			summary.Type = entity.EntityType;
			if ( entity.EntityType == EntityType.BlockGrid ) { //костылик, да
				summary.Position = entity.transform.position +  new Vector3(8,8,8);
			}
			summary.Summary = entity.GetSummary();
			if ( entity.EntityType == EntityType.Ship ) {
				var s = (ShipCore)entity;
				if ( s != null ) {
					if ( CommonStaticData.IsHostileTeam(_owner.Team, s.Team) ) {
						summary.IsEnemy = true;
					}
				}
			}
			return summary;
		}

		bool NeedResetTrackedObject() {
			if ( _trackedObjectId == 0 ) {
				return false;
			}
			var e = ServerEntityController.Instance.GetEntityById(_trackedObjectId);
			if ( !e ) {
				return true;
			}
			if ( Vector3.Distance(_owner.transform.position, e.transform.position) > TrackerRange ) {
				return true;
			}

			return false;
		}
	}
}
